package controllers

import (
	"errors"
	"fmt"
	"ginstudydemo.com/main/cache"
	"ginstudydemo.com/main/dao"
	"ginstudydemo.com/main/models"
	"ginstudydemo.com/main/service"
	"github.com/gin-gonic/gin"
	"github.com/mssola/useragent"
	"github.com/oschwald/geoip2-golang"
	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"
	"io"
	"log"
	"math/rand"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"
)

type IResourceController interface {
	CreateTestDataHandler()
}

type ResourceController struct {
	db       *gorm.DB
	rdb      *redis.Client
	updates  chan uint64
	viewLogs chan models.ResourceViewLog // 新增浏览记录通道
	wg       sync.WaitGroup
	geoipDb  *geoip2.Reader
}

func NewResourceController() *ResourceController {
	// 初始化GeoIP数据库
	geoDb, err := geoip2.Open("GeoLite2-City.mmdb")
	if err != nil {
		log.Fatalf("Failed to initialize GeoIP database: %v", err)
	}

	c := &ResourceController{
		db:       dao.Db,
		rdb:      cache.Rdb,
		updates:  make(chan uint64, 1000),
		viewLogs: make(chan models.ResourceViewLog, 5000),
		geoipDb:  geoDb,
	}

	go c.processUpdates()
	go c.processViewLogs() // 启动浏览记录处理

	return c
}

// IncrementViewCountHandler 增加资源浏览量（带缓存和异步更新）
func (u *ResourceController) IncrementViewCountHandler(c *gin.Context) {
	resourceId := c.Param("resourceId")
	resourceIdInt, err := strconv.ParseUint(resourceId, 10, 64)
	if err != nil {
		service.ReturnErr(c, "9999", "无效的资源ID")
		return
	}

	// 解析请求参数
	var logReq models.ViewLogRequestParam
	if err := c.ShouldBindJSON(&logReq); err != nil && !errors.Is(err, io.EOF) {
		service.ReturnErr(c, "9999", "参数解析失败")
		return
	}

	// 1. 更新缓存
	cacheKey := "resource:views:" + resourceId
	if _, err = u.rdb.Incr(cache.Rctx, cacheKey).Result(); err != nil {
		service.ReturnErr(c, "9999", "缓存更新失败")
		return
	}

	// 2. 异步记录浏览信息
	go u.recordViewLog(c, resourceIdInt, logReq)

	// 3. 异步更新数据库
	u.updates <- resourceIdInt

	service.ReturnSuccess(c, "2000", "ok", struct{}{})
}

// recordViewLog 记录浏览信息
func (u *ResourceController) recordViewLog(c *gin.Context, resourceId uint64, req models.ViewLogRequestParam) {
	// 使用useragent库解析
	ua := useragent.New(c.Request.UserAgent())
	browser, _ := ua.Browser()

	// 解析设备类型
	deviceType := parseDeviceType(ua)

	// 解析地理位置
	ip := c.ClientIP()
	country, region, city := u.parseGeoIP(ip)

	// 创建浏览记录
	viewLog := models.ResourceViewLog{
		ResourceID:   resourceId,
		UserID:       req.UserID,
		SessionID:    getSessionID(c),
		IPAddress:    ip,
		UserAgent:    c.Request.UserAgent(),
		Referer:      c.Request.Referer(),
		DeviceType:   deviceType,
		OS:           ua.OS(),
		Browser:      browser,
		Country:      country,
		Region:       region,
		City:         city,
		ViewDuration: req.ViewDuration,
		IsOrganic:    req.IsOrganic,
		UtmSource:    req.UtmSource,
		UtmMedium:    req.UtmMedium,
		UtmCampaign:  req.UtmCampaign,
	}

	// 发送到异步处理通道
	u.viewLogs <- viewLog
}

// parseDeviceType 解析设备类型
func parseDeviceType(ua *useragent.UserAgent) *int {
	deviceType := 4 // 其他
	switch {
	case ua.Mobile():
		deviceType = 2 // 手机
	case ua.Bot():
		deviceType = 4 // 机器人
	default:
		if isTablet(ua) {
			deviceType = 3 // 平板
		} else {
			deviceType = 1 // PC
		}
	}
	return &deviceType
}

// isTablet 判断平板设备
func isTablet(ua *useragent.UserAgent) bool {
	return strings.Contains(ua.OS(), "iPad") ||
		(strings.Contains(ua.OS(), "Android") && !ua.Mobile())
}

// parseGeoIP 解析IP地理位置
func (u *ResourceController) parseGeoIP(ipStr string) (country, region, city string) {
	ip := net.ParseIP(ipStr)
	if ip == nil {
		return "", "", ""
	}

	record, err := u.geoipDb.City(ip)
	if err != nil {
		return "", "", ""
	}

	country = record.Country.Names["en"]
	if len(record.Subdivisions) > 0 {
		region = record.Subdivisions[0].Names["en"]
	}
	city = record.City.Names["en"]
	return
}

// 获取SessionID（根据实际系统实现）
func getSessionID(c *gin.Context) string {
	// 示例：从Cookie获取
	if session, err := c.Cookie("session_id"); err == nil {
		return session
	}
	// 或从Header获取
	// return c.GetHeader("X-Session-ID")
	return ""
}

// 浏览记录批量处理
func (u *ResourceController) processViewLogs() {
	batch := make([]models.ResourceViewLog, 0, 200)
	ticker := time.NewTicker(10 * time.Second)

	for {
		select {
		case log := <-u.viewLogs:
			batch = append(batch, log)
			if len(batch) >= 200 {
				u.flushLogs(batch)
				batch = batch[:0]
			}
		case <-ticker.C:
			if len(batch) > 0 {
				u.flushLogs(batch)
				batch = batch[:0]
			}
		}
	}
}

func (u *ResourceController) flushLogs(logs []models.ResourceViewLog) {
	if len(logs) == 0 {
		return
	}

	u.wg.Add(1)
	defer u.wg.Done()

	// 使用事务批量插入
	err := u.db.Transaction(func(tx *gorm.DB) error {
		return tx.CreateInBatches(logs, 100).Error
	})

	if err != nil {
		log.Printf("批量保存浏览记录失败: %v", err)
	}
}

// processUpdates 处理异步更新
func (u *ResourceController) processUpdates() {
	batch := make([]uint64, 0, 100) // 批量处理100条
	ticker := time.NewTicker(5 * time.Second)

	for {
		select {
		case id := <-u.updates:
			batch = append(batch, id)
			if len(batch) >= 100 {
				u.flushBatch(batch)
				batch = batch[:0]
			}
		case <-ticker.C:
			if len(batch) > 0 {
				u.flushBatch(batch)
				batch = batch[:0]
			}
		}
	}
}

// flushBatch 批量更新数据库
func (u *ResourceController) flushBatch(ids []uint64) {
	u.wg.Add(1)
	defer u.wg.Done()

	// 去重
	idMap := make(map[uint64]struct{})
	for _, id := range ids {
		idMap[id] = struct{}{}
	}

	// 批量更新
	for id := range idMap {
		u.db.Model(&models.Resource{}).
			Where("id = ? AND is_deleted = 0", id).
			Update("view_count", gorm.Expr("view_count + 1"))
	}
}

// Close 关闭控制器，等待所有更新完成
func (u *ResourceController) Close() {
	close(u.updates)
	u.wg.Wait()
}

// GetUsePageByCateHandler 分页获取指定分类资源数据
func (u ResourceController) GetUsePageByCateHandler(c *gin.Context) {

	var query models.ResourcePageByCateParams
	var pagination models.PaginationParams
	// 获取查询参数（除分页参数）
	if err := c.ShouldBindQuery(&query); err != nil {
		service.ReturnErr(c, "9999", err.Error())
		c.Abort() // 终止后续处理
		return    // 确保不会继续执行后续代码
	}

	// 获取分页参数
	if qErr := c.ShouldBindQuery(&pagination); qErr != nil {
		service.ReturnErr(c, "9999", qErr.Error())
		c.Abort() // 终止后续处理
		return    // 确保不会继续执行后续代码
	}

	data, count, pageNum, pageSize, modelErr := models.NewResourceModel().GetUsePageByCate(query, pagination)

	if !(modelErr == nil) {
		service.ReturnErr(c, "9999", modelErr.Error())
		c.Abort()
		return
	}
	// 是否还有下一页
	hasNextPage := int64(pageNum)*int64(pageSize) < count

	service.ReturnSuccess[models.ResourceResList](c, "2000", "ok", models.ResourceResList{
		Total:       count,
		PageSize:    pageSize,
		PageNum:     pageNum,
		List:        data,
		HasNextPage: hasNextPage,
	})

}

// GetUserViewHistoryHandler 分页用户浏览指定分类资源数据
func (u ResourceController) GetUserViewHistoryHandler(c *gin.Context) {
	var query models.GetUserViewHistoryParams
	var pagination models.PaginationParams

	// 绑定前打印原始查询参数
	log.Printf("Raw Query: %+v", c.Request.URL.Query())

	// 绑定查询参数
	if err := c.ShouldBindQuery(&query); err != nil {
		service.ReturnErr(c, "9999", "参数错误: "+err.Error())
		return
	}

	// 获取分页参数
	if qErr := c.ShouldBindQuery(&pagination); qErr != nil {
		service.ReturnErr(c, "9999", qErr.Error())
		c.Abort() // 终止后续处理
		return    // 确保不会继续执行后续代码
	}

	// 验证必须参数
	if query.UserID == 0 {
		service.ReturnErr(c, "9999", "需要用户登录1")
		return
	}

	// 执行查询
	data, total, pageNum, pageSize, err := models.NewResourceModel().GetUserViewHistory(query, pagination)
	if err != nil {
		service.ReturnErr(c, "9999", err.Error())
		return
	}

	// 构建响应
	hasNext := int64(pageNum)*int64(pageSize) < total
	//service.ReturnSuccess(c, "2000", "ok", gin.H{
	//	"list":        data,
	//	"total":       total,
	//	"pageNum":     pageNum,
	//	"pageSize":    pageSize,
	//	"hasNextPage": hasNext,
	//})

	service.ReturnSuccess[models.ViewHistoryResourceResList](c, "2000", "ok", models.ViewHistoryResourceResList{
		Total:       total,
		PageSize:    pageSize,
		PageNum:     pageNum,
		List:        data,
		HasNextPage: hasNext,
	})

}

// GetByCateHandler 不分页获取指定分类资源数据(数据量少的分类)
func (u ResourceController) GetByCateHandler(c *gin.Context) {
	var query models.ResourcePageByCateParams
	var pagination models.PaginationParams
	// 获取查询参数（除分页参数）
	if err := c.ShouldBindQuery(&query); err != nil {
		service.ReturnErr(c, "9999", err.Error())
		c.Abort() // 终止后续处理
		return    // 确保不会继续执行后续代码
	}

	//// 获取分页参数
	pagination.PageSize = 50 //不分页最大个数为50
	pagination.PageNum = 1
	//if qErr := c.ShouldBindQuery(&pagination); qErr != nil {
	//	service.ReturnErr(c, "9999", qErr.Error())
	//	c.Abort() // 终止后续处理
	//	return    // 确保不会继续执行后续代码
	//}

	data, _, _, _, modelErr := models.NewResourceModel().GetUsePageByCate(query, pagination)

	if !(modelErr == nil) {
		service.ReturnErr(c, "9999", modelErr.Error())
		c.Abort()
		return
	}
	// 是否还有下一页
	//hasNextPage := int64(pageNum)*int64(pageSize) < count

	service.ReturnSuccess[[]models.ResourceRes](c, "2000", "ok", data)
}

// GetUsePageByTopicHandler 分页获取指定专辑下的数据
func (u ResourceController) GetUsePageByTopicHandler(c *gin.Context) {
	var query models.ResourcePageByTopic
	var pagination models.PaginationParams
	// 获取查询参数（除分页参数）
	if err := c.ShouldBindQuery(&query); err != nil {
		service.ReturnErr(c, "9999", err.Error())
		c.Abort() // 终止后续处理
		return    // 确保不会继续执行后续代码
	}

	// 获取分页参数
	if qErr := c.ShouldBindQuery(&pagination); qErr != nil {
		service.ReturnErr(c, "9999", qErr.Error())
		c.Abort() // 终止后续处理
		return    // 确保不会继续执行后续代码
	}

	data, count, pageNum, pageSize, modelErr := models.NewResourceModel().GetUsePageByTopic(query, pagination)

	if !(modelErr == nil) {
		service.ReturnErr(c, "9999", modelErr.Error())
		c.Abort()
		return
	}
	// 是否还有下一页
	hasNextPage := int64(pageNum)*int64(pageSize) < count

	service.ReturnSuccess[models.ResourceResList](c, "2000", "ok", models.ResourceResList{
		Total:       count,
		PageSize:    pageSize,
		PageNum:     pageNum,
		List:        data,
		HasNextPage: hasNextPage,
	})
}

// IncrementViewCountHandler 增加资源浏览量
//func (u ResourceController) IncrementViewCountHandler(c *gin.Context) {
//
//	resourceId := c.Param("resourceId")
//	resourceIdInt, _ := strconv.ParseInt(resourceId, 10, 64)
//
//	err := dao.Db.Debug().Model(&models.Resource{}).
//		Where("id = ? AND is_deleted = 0", resourceIdInt).
//		Update("view_count", gorm.Expr("view_count + 1")).Error
//
//	if err != nil {
//		service.ReturnErr(c, "9999", err.Error())
//		c.Abort() // 终止后续处理
//		return    // 确保不会继续执行后续代码
//	}
//
//	service.ReturnSuccess(c, "2000", "ok", struct{}{})
//
//}

// CreateTestDataHandler 随机生成资源数据
func (u ResourceController) CreateTestDataHandler(c *gin.Context) {

	var input struct {
		ResourceType int    `json:"resource_type"`
		CategoryId   uint   `json:"category_id"`
		CoverImage   string `json:"cover_image"`
		WebUrl       string `json:"web_url"`
		MediaUrl     string `json:"media_url"`
		InsertCount  int    `json:"insert_count"`
	}

	//param := &input
	// 把结构体指针传入BindJSON，这样param的值才能同步修改
	//cErr := c.BindJSON(param)
	//if !(cErr == nil) {
	//	service.ReturnErr(c, "9999", cErr.Error())
	//	c.Abort()
	//	return
	//}

	//if err := c.ShouldBindJSON(&input); err != nil {
	//	service.ReturnErr(c, "3000", "数据传入错误")
	//	return
	//}

	// 分类 ID 列表
	categoryIDs := []uint{
		2, 4, 5, 8, 9, 10,
		11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
		21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
		31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
		41, 42, 43, 44, 45, 46,
	}

	//input.CategoryId = 4
	input.InsertCount = 30
	input.CoverImage = "https://sanjitian-1256310189.cos.ap-chengdu.myqcloud.com/gin_test/2025/02/2025_02_19_15_36_08_2712_2.jpg"
	input.WebUrl = "http://www.cntcm.com.cn/news.html?aid=193731"
	input.MediaUrl = "https://sanjitian-1256310189.cos.ap-chengdu.myqcloud.com/gin_test/beyond_haikuotiankong.mp4"
	input.ResourceType = 1

	if input.InsertCount <= 0 {
		input.InsertCount = 1
	}

	// 遍历每个分类 ID，插入数据
	for _, categoryID := range categoryIDs {
		resources := make([]models.Resource, 0, input.InsertCount)

		for i := 0; i < input.InsertCount; i++ {
			resources = append(resources, models.Resource{
				Title:            "资源_" + randomString(8),
				Introduction:     "简介_" + randomString(20),
				CoverImage:       input.CoverImage,
				CardCoverImage:   input.CoverImage,
				BannerCoverImage: input.CoverImage,
				ResourceType:     input.ResourceType,
				CategoryId:       categoryID,
				WebUrl:           input.WebUrl,
				MediaUrl:         input.MediaUrl,
				ReleaseDate:      time.Now(),
				CreateBy:         1,
				CreateTime:       time.Now(),
				UpdateBy:         1,
				UpdateTime:       time.Now(),
			})
		}

		err := dao.Db.Table("resource").Create(&resources).Error // 通过数据的指针来创建

		if err != nil {
			service.ReturnErr(c, "4002", "插入错误")
			return
		}

		// 批量插入数据
		//if err := dao.Db.Table("resource").CreateInBatches(&resources, 100).Error; err != nil {
		//	dao.Db.Rollback() // 发生错误，回滚事务
		//	service.ReturnErr(c, "4002", "插入错误："+err.Error())
		//	return
		//}
	}

	// 提交事务
	//if err := dao.Db.Commit().Error; err != nil {
	//	service.ReturnErr(c, "4003", "事务提交失败："+err.Error())
	//	return
	//}

	//resources := make([]models.Resource, 0, input.InsertCount)
	//
	//for i := 0; i < input.InsertCount; i++ {
	//	resources = append(resources, models.Resource{
	//		Title:            "资源_" + randomString(8),
	//		Introduction:     "简介_" + randomString(20),
	//		CoverImage:       input.CoverImage,
	//		CardCoverImage:   input.CoverImage,
	//		BannerCoverImage: input.CoverImage,
	//		ResourceType:     input.ResourceType,
	//		CategoryId:       input.CategoryId,
	//		WebUrl:           input.WebUrl,
	//		MediaUrl:         input.MediaUrl,
	//		ReleaseDate:      time.Now(),
	//		CreateBy:         1,
	//		CreateTime:       time.Now(),
	//		UpdateBy:         1,
	//		UpdateTime:       time.Now(),
	//	})
	//}
	//
	//err := dao.Db.Table("resource").Create(&resources).Error // 通过数据的指针来创建
	//
	//if err != nil {
	//	service.ReturnErr(c, "4002", "插入错误")
	//	return
	//}
	service.ReturnErr(c, "2000", fmt.Sprintf("成功插入 %d 个分类，共 %d 条数据", len(categoryIDs), len(categoryIDs)*input.InsertCount))

	//service.ReturnErr(c, "2000", "OK")
	return

}

func randomString(n int) string {
	letters := []rune("惊蛰前后阴寒之气渐降，阳气升发，人体血管舒张，流入大脑的血供较冬季减少，中枢神经的兴奋性减弱，致使春困扰人。很多人认为多睡可以解乏，但结果往往是越睡越困。其实，春天可以适当晚睡早起、多伸懒腰，这样可以使血液循环加快，不但能减轻困意，还能激发肝脏机能")
	b := make([]rune, n)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	return string(b)
}
